"""
Modern Portfolio Theory(MPT)
现代组合理论：
由Markowitz在1952年提出，核心思想是以最小化标准差(方差)并且最大化收益预期为目标来进行资产配置，
即 均值-方差分析(Mean-Variance Analysis)，是金融经济学的重要基础理论。

在对预期收益和风险给出数学表达式之后，根据资产配置的目标(固定风险下，最大化收益；固定收益情况下，最小化风险)，
可以使用最优化理论来求解资产配置中每个资产比例。
"""
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy.optimize import minimize


import rqdatac
rqdatac.init()

# 2个资产时候的有效前沿

# 多个资产时候的有效前沿

# 拉格朗日乘子法

from util.MTDate_trade import *
start = "2022-06-01"
real_start = MT_util_get_real_date(start, towards=1)
pre_start = trade_date_sse[trade_date_sse.index(real_start) - 1]
end = "2023-01-01"
real_end = MT_util_get_real_date(end, towards=-1)

# 资产组合的数目
num_portfolios = 5000

# 获取米框的日线数据
code_list = rqdatac.index_components('000300.XSHG')[:10]
close = rqdatac.get_price(code_list, start_date=real_start, end_date=real_end, fields='close', adjust_type='post')
# close = close.reset_index()
daily_return = close.groupby('order_book_id').apply(lambda x: x['close'].pct_change()).dropna()
daily_return.index = daily_return.index.droplevel()
annual_return = daily_return.unstack().T.mean()*250.0
final_code_list = daily_return.unstack().T.columns.tolist()
# 协方差矩阵
X = np.vstack([df.values for code,df in daily_return.groupby('order_book_id')])
annual_cov = np.cov(X, None, True, False)
print(annual_cov)

# 计算不同组合的收益率与波动率
# 组合收益
port_return = []
# 组合波动
port_volatility = []
# 夏普率
sharpe_ratio = []
# 组合中股票权重
stock_weights = []

def get_return_risk_sharpe(weights, risk_free=0.04):  # 假设年化无风险利率为 4%
    # https://zhuanlan.zhihu.com/p/411301706
    # https://www.zhihu.com/question/20852004/answer/134902061
    returns = np.dot(weights, annual_return)  # r_p = sum(w_i*r_p)
    # 计算波动率
    volatility = np.sqrt(
        np.dot(weights.T, np.dot(annual_cov, weights))
    )  # sigma_P = w^TVw
    # 计算夏普比率
    sr = (returns - risk_free) / volatility
    return returns, volatility, sr

# 对所有资产组合进行随机权重分配，计算预期收益、波动率和夏普率
for port in range(num_portfolios):
    weights = np.random.random(len(final_code_list))
    weights /= np.sum(weights)
    returns, volatility, sr = get_return_risk_sharpe(weights)
    sharpe_ratio.append(sr)
    port_return.append(returns)
    port_volatility.append(volatility)
    stock_weights.append(weights)

portfolio = {
    "Returns": port_return,
    "Volatility": port_volatility,
    "Sharpe Ratio": sharpe_ratio,
}

# 将预期收益、波动率和夏普率以及个股权重拼接为 DataFrame
for counter, symbol in enumerate(final_code_list):
    portfolio[symbol + " Weight"] = [weight[counter] for weight in stock_weights]

df = pd.DataFrame(portfolio)
column_order = ["Returns", "Volatility", "Sharpe Ratio"] + [
    stock + " Weight" for stock in final_code_list
]
df = df[column_order]

# # 可视化，观察均值-方差曲线的关于夏普率的热度图
# plt.style.use("ggplot")
# df.plot.scatter(
#     x="Volatility",
#     y="Returns",
#     c="Sharpe Ratio",
#     cmap="autumn",
#     edgecolors="black",
#     figsize=(15, 9),
#     grid=True,
# )
# plt.xlabel("Volatility (Std. Deviation)")
# plt.ylabel("Expected Returns")
# plt.title("Efficient Frontier")
# plt.show()


# 可视化，计算有效前沿
# 定义优化目标函数 (固定收益率)
# 最小化风险函数
def min_std(weights):
    return get_return_risk_sharpe(weights)[1]


# 约束条件
constraints = {"type": "eq", "fun": lambda x: np.sum(x) - 1}  # 组合中资产权重之和为 1

# 将参数值(权重)限制在0.001和1之间,保证前沿上的点包含了所有资产
bins = tuple((0.001, 1.0) for x in range(len(final_code_list)))

# 权重初始值设置为均为分布, 最优化方法采用 Sequential Least-Squares Quadratic Programming (SLSQP)
min_risk = minimize(
    min_std,
    len(final_code_list) * [1.0 / len(final_code_list)],
    method="SLSQP",
    bounds=bins,
    constraints=constraints,
)

# 计算有效前沿上的投资组合
def get_efficient_frontier():
    stock_num = len(final_code_list)
    frontier_returns_array = np.linspace(0.0, 0.5, 50)  # 预期收益率序列
    frontier_sigma_array = []

    # 遍历每个收益率，去寻找风险最小的投资组合
    for ret in frontier_returns_array:
        # 约束条件：1.收益率等于ret；2.各权重和等于1
        frontier_cons = (
            {"type": "eq", "fun": lambda x: get_return_risk_sharpe(x)[0] - ret},
            {"type": "eq", "fun": lambda x: np.sum(x) - 1},
        )
        frontier_point = minimize(
            min_std,
            stock_num * [1.0 / stock_num],
            method="SLSQP",
            bounds=bins,
            constraints=frontier_cons,
        )
        # print(frontier_point['x'])
        frontier_sigma_array.append(frontier_point["fun"])

    frontier_sigma_array = np.array(frontier_sigma_array)
    return frontier_returns_array, frontier_sigma_array


frontier_return, frontier_sigma = get_efficient_frontier()
# 绘制图形
plt.figure(figsize=(20, 10))
plt.style.use("ggplot")
df.plot.scatter(
    x="Volatility",
    y="Returns",
    c="Sharpe Ratio",
    cmap="autumn",
    edgecolors="black",
    figsize=(15, 9),
    grid=True,
)
plt.xlabel("Volatility (Std. Deviation)")
plt.ylabel("Expected Returns")
plt.title("Efficient Frontier")

# 绿点代表最小方差组合
min_sigma_point, = plt.plot(
    get_return_risk_sharpe(min_risk["x"])[1],
    get_return_risk_sharpe(min_risk["x"])[0],
    "g.",
    markersize=15.0,
)

# 叉号代表有效前沿
plt.scatter(frontier_sigma, frontier_return, marker="x")
plt.show()